package com.jthinker.blog.web.controller.admin.common;

import com.alibaba.fastjson.JSON;
import com.jthinker.blog.bean.enu.FilterOperator;
import com.jthinker.blog.bean.enu.SortOrder;
import com.jthinker.blog.bean.vo.Filter;
import com.jthinker.blog.bean.vo.FilterGroup;
import com.jthinker.blog.bean.vo.FilterRule;
import com.jthinker.blog.service.BaseService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.*;

/**
 * User: Gang Chen
 * Date: 2015/6/28 14:36
 */
public abstract class BaseController
{
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseController.class);

    private static final String PARAM_NAME_IS_ERROR = "isError";

    private static final String PARAM_NAME_DATA = "data";

    private static final String PARAM_NAME_MESSAGE = "message";

    private static final String PARAM_PAGE = "page";
    private static final String PARAM_PAGE_SIZE = "pagesize";

    private static final String PARAM_SORT = "sortname";
    private static final String PARAM_SORT_ORDER = "sortorder";

    private static final String PARAM_NAME_WHERE = "where";

    private static final int DEFAULT_PAGE = 1;
    private static final int DEFAULT_PAGE_SIZE = 10;

    private static final String HTML_SEPARATOR = "<br>";

    private static final String PARAM_PREFIX_TOKEN = ":";
    private static final String GROUP_LEFT_TOKEN = "(";
    private static final String GROUP_RIGHT_TOKEN = ")";
    private static final String LIKE_TOKEN = "%";

    public Map<String, Object> getErrorMessage(String message)
    {
        return getErrorMessage(message, null);
    }

    public Map<String, Object> getErrorMessage(Object data)
    {
        return getErrorMessage(null, data);
    }

    public Map<String, Object> getErrorMessage(String message, Object data)
    {
        return getMessage(Boolean.TRUE, message, data);
    }

    public Map<String, Object> getSuccessMessage(Object data)
    {
        return getSuccessMessage(null, data);
    }

    public Map<String, Object> getSuccessMessage(String message)
    {
        return getSuccessMessage(message, null);
    }

    public Map<String, Object> getSuccessMessage(String message, Object data)
    {
        return getMessage(Boolean.FALSE, message, data);
    }

    public Map<String, Object> getMessage(boolean isError, String message, Object data)
    {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(PARAM_NAME_IS_ERROR, Boolean.valueOf(isError));
        map.put(PARAM_NAME_MESSAGE, message);
        map.put(PARAM_NAME_DATA, data);
        return map;
    }

    public <M> Map<String, Object> getGridData(int total, List<M> rows)
    {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("Total", total);
        map.put("Rows", rows);
        return map;
    }

    public String param(HttpServletRequest request, String key, String... defaultValues)
    {
        String val = request.getParameter(key);
        return val != null ? val : (defaultValues.length > 0 ? defaultValues[0] : null);
    }

    public int param(HttpServletRequest request, String key, int defaultValue)
    {
        return NumberUtils.toInt(param(request, key), defaultValue);
    }

    public <T extends Filter> T getFilter(HttpServletRequest request, Class<T> clazz)
    {
        int page = param(request, PARAM_PAGE, DEFAULT_PAGE);
        int pageSize = param(request, PARAM_PAGE_SIZE, DEFAULT_PAGE_SIZE);
        String sort = param(request, PARAM_SORT);
        SortOrder sortOrder = null;
        String sortOrderStr = param(request, PARAM_SORT_ORDER);

        if (StringUtils.isNotBlank(sortOrderStr))
        {
            sortOrder = SortOrder.parse(sortOrderStr);
        }

        T filter;
        try
        {
            filter = clazz.newInstance();
        }
        catch (Exception e)
        {
            LOGGER.error("{}", e);
            throw new RuntimeException(e);
        }

        filter.setPage(page);
        filter.setPageSize(pageSize);
        filter.setSort(sort);
        filter.setSortOrder(sortOrder);

        return filter;
    }

    public Filter getFilter(HttpServletRequest request)
    {
        return getFilter(request, Filter.class);
    }

    public <M extends Serializable, PK extends Serializable> Map<String, Object> getListDataByEntity(HttpServletRequest request,
            BaseService<M, PK> baseService, Class<M> clazz)
    {
        String name = clazz.getSimpleName();
        String countHql = "select count(*) from " + name + " where 1=1";
        String queryHql = "from " + name + " where 1=1";
        return getListDataByHql(request, baseService, countHql, queryHql);
    }

    public <M extends Serializable, PK extends Serializable> Map<String, Object> getListDataByHql(HttpServletRequest request,
            BaseService<M, PK> baseService, String _countHql, String _queryHql)
    {
        StringBuilder countSql = new StringBuilder(_countHql);
        StringBuilder querySql = new StringBuilder(_queryHql);

        StringBuilder sufix = new StringBuilder();

        String where = param(request, PARAM_NAME_WHERE);
        FilterGroup filterGroup = null;

        if (StringUtils.isNotBlank(where))
        {
            filterGroup = JSON.parseObject(where, FilterGroup.class);
        }

        sufix.append(" and ");

        Map<String, Object> params = new HashMap<String, Object>();
        sufix.append(translateGroup(filterGroup, params));

        countSql.append(sufix);
        querySql.append(sufix);

        LOGGER.debug("count hql : {}", countSql);
        LOGGER.debug("query hql : {}", querySql);
        int total = baseService.count(countSql.toString(), params);

        if (total == 0)
        {
            return getGridData(0, Collections.emptyList());
        }

        Filter filter = getFilter(request);

        List<M> list = baseService.list(querySql.toString(), filter.getPage(), filter.getPageSize(), params);

        return getGridData(total, list);
    }

    public <M> List<Map<String, Object>> getSelectData(HttpServletRequest request, BaseService baseService, String keyName, String textName,
            Class<M> clazz)
    {
        String hql = MessageFormat
                .format("SELECT new Map({0} as id,{1} as text,{0} as value) FROM {2}", keyName, textName, clazz.getSimpleName());
        return baseService.list(hql);
    }

    public Map<String, Object> generateErrorMessage(BindingResult bindingResult)
    {
        StringBuilder message = new StringBuilder();
        List<ObjectError> errors = bindingResult.getAllErrors();

        for (ObjectError error : errors)
        {
            message.append(error.getDefaultMessage());
            message.append(HTML_SEPARATOR);
        }

        return getErrorMessage(message.toString());
    }

    private String translateGroup(FilterGroup filterGroup, Map<String, Object> params)
    {
        if (filterGroup == null)
        {
            return " 1=1 ";
        }

        FilterOperator op = filterGroup.getOp();

        StringBuilder where = new StringBuilder();

        boolean isStart = true;
        where.append(GROUP_LEFT_TOKEN);

        List<FilterRule> rules = filterGroup.getRules();
        if (CollectionUtils.isNotEmpty(rules))
        {
            for (FilterRule rule : rules)
            {
                if (!isStart)
                {
                    where.append(getOperatorText(op));
                }

                where.append(translateRule(rule, params));
                isStart = false;
            }
        }

        List<FilterGroup> groups = filterGroup.getGroups();
        if (CollectionUtils.isNotEmpty(groups))
        {
            for (FilterGroup subGroup : groups)
            {
                if (!isStart)
                {
                    where.append(getOperatorText(op));
                }

                where.append(translateGroup(subGroup, params));
                isStart = false;
            }
        }

        where.append(GROUP_RIGHT_TOKEN);
        if (isStart)
        {
            return " 1=1 ";
        }

        return where.toString();

    }

    public String translateRule(FilterRule filterRule, Map<String, Object> params)
    {
        if (filterRule == null)
        {
            return " 1=1 ";
        }

        String field = filterRule.getField();
        FilterOperator op = filterRule.getOp();
        String type = filterRule.getType();
        String value = filterRule.getValue().toString();

        StringBuilder builder = new StringBuilder();

        if (StringUtils.contains(field, "_"))
        {
            builder.append(StringUtils.replace(field, "_", "."));
        }
        else
        {
            builder.append(field);
        }

        //防止.号报错
        //        if (StringUtils.contains(field, "."))
        //        {
        //            field = StringUtils.substring(field, StringUtils.indexOf(field, ".") + 1);
        //        }

        //插入op
        builder.append(getOperatorText(op));

        switch (filterRule.getOp())
        {
            case like:
                if (!StringUtils.startsWith(value, LIKE_TOKEN))
                {
                    value = LIKE_TOKEN + value;
                }

                if (!StringUtils.endsWith(value, LIKE_TOKEN))
                {
                    value = value + LIKE_TOKEN;
                }
                builder.append(PARAM_PREFIX_TOKEN).append(field);

                params.put(field, value);
                break;

            case endwith:
                if (!StringUtils.startsWith(value, LIKE_TOKEN))
                {
                    value = LIKE_TOKEN + value;
                }
                builder.append(PARAM_PREFIX_TOKEN).append(field);

                params.put(field, value);
                break;

            case startwith:
                if (!StringUtils.endsWith(value, LIKE_TOKEN))
                {
                    value = value + LIKE_TOKEN;
                }
                builder.append(PARAM_PREFIX_TOKEN).append(field);
                params.put(field, value);
                break;

            case equal:
            case notequal:
            case greaterorequal:
            case greater:
            case lessorequal:
            case less:
                builder.append(PARAM_PREFIX_TOKEN).append(field);
                params.put(field, createParam(type, value));
                break;

            case in:
            case notin:
                String[] arr = StringUtils.split(value, ",");
                builder.append(GROUP_LEFT_TOKEN);
                builder.append(PARAM_PREFIX_TOKEN).append(field);
                builder.append(GROUP_RIGHT_TOKEN);

                List<Object> list = new ArrayList<Object>(arr.length);

                for (String str : arr)
                {
                    list.add(createParam(type, str));
                }

                params.put(field, list);
                break;
        }

        return builder.toString();
    }

    private Object createParam(String type, String value)
    {
        type = type.toLowerCase();
        if ("long".equals(type))
        {
            return NumberUtils.toLong(value);
        }

        if ("int".equals(type))
        {
            return NumberUtils.toInt(value);
        }

        if ("string".equals(type))
        {
            return value.toString();
        }

        return value;
    }

    public static String getOperatorText(FilterOperator op)
    {
        switch (op)
        {
            case equal:
                return " = ";
            case notequal:
                return " <> ";
            case startwith:
                return " LIKE ";
            case endwith:
                return " LIKE ";
            case like:
                return " LIKE ";
            case greater:
                return " > ";
            case greaterorequal:
                return " >= ";
            case less:
                return " < ";
            case lessorequal:
                return " <= ";
            case in:
                return " in ";
            case notin:
                return " not in ";
            case isnull:
                return " is null ";
            case isnotnull:
                return " is not null ";
            case and:
                return " and ";
            case or:
                return " or ";
            default:
                return " = ";
        }
    }
}
